/**
 * 加解密模块，支持md5编码、base64编码与解码、sha编码等
 */

package ms.core.tool;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Random;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class EncryptTool {
	final public static String MD5	  ="MD5";
	final public static String DES	  ="DES";
	final public static String SHA1	  ="SHA";
	final public static String SHA256 ="SHA-256";
	final public static String SHA512 ="SHA-512";

	/**
	 * md5加密接口
	 * @param str source encode string
	 * @return md5 encode string
	 */
	public static String md5Encode(String str){
		if ((str==null)||(str.length()==0)) return "";

		try{
			MessageDigest md5 = null;
			md5 = MessageDigest.getInstance(MD5);
			byte[] byteArray = str.getBytes("UTF-8");
			byte[] md5Bytes = md5.digest(byteArray);
			StringBuffer hexValue = new StringBuffer();
			for (int i=0; i<md5Bytes.length; i++){
				int val = md5Bytes[i] & 0xff;
				if (val < 16) {
					hexValue.append("0");
				}
				hexValue.append(Integer.toHexString(val));
			}
            return hexValue.toString();
		}catch(Exception e){
			Log4j2Tool.error(EncryptTool.class, "md5Encode:", e);
        	return "";
        }
    }

	/**
	 * 密码加密接口
	 * @param password 明文密码
	 * @param keySalt 密码干扰串
	 * @return
	 */
	public static String encodePassword(String password, String keySalt){
		if (keySalt==null) keySalt = "";
		return md5Encode(StrTool.concat(password,keySalt));
	}

	/**
	 * SHA 加密
	 *
	 * @param str 加密字符串
	 * @param type 加密类型
	 * @return
	 */
	private static String sha(String str, String type){
		if ((str==null)||(str.length()==0)||
			(type==null)||(type.length()==0))
			return "";

		try{
			MessageDigest sha = null;
			sha = MessageDigest.getInstance(type);
			byte[] byteArray = str.getBytes("UTF-8");
			byte[] md5Bytes = sha.digest(byteArray);
			StringBuffer hexValue = new StringBuffer();
			for (int i = 0; i < md5Bytes.length; i++) {
				int val = md5Bytes[i] & 0xff;
				if (val < 16) { 
					hexValue.append("0");
				}
				hexValue.append(Integer.toHexString(val));
			}
			return hexValue.toString();
		}catch(Exception e){
			Log4j2Tool.error(EncryptTool.class, "sha:", e);
			return "";
		}
	}

	/**SHAEncode function
	 * @param str source encode string
	 * @return SHA encode string
	 */
	public static String sha1(String str){
		return sha(str,SHA1);
	}

	/**SHAEncode function
	 * @param str source encode string
	 * @return SHA encode string
	 */
	public static String sha256(String str){
		return sha(str,SHA256);
	}

	/**SHAEncode function
	 * @param str source encode string
	 * @return SHA encode string
	 */
	public static String sha512(String str){
		return sha(str,SHA512);
	}

	/**
	 * base64编码
	 * @param str the string to be encode
	 * @return
	 */
    public static String base64Encode(String str){  
		if ((str==null)||(str.equals(""))) 
			return "";

		try{
	    	byte[] bytes = str.getBytes("UTF-8");
	    	return Base64.getEncoder().encodeToString(bytes);
		}catch(Exception e){
			Log4j2Tool.error(EncryptTool.class, "base64Encode:", e);
			return "";
		}
    }  

    /**
     * base64编码
     * @param bytes 编码字节数组
     * @return
     */
    public static String base64Encode(byte[] bytes){  
		try{
	    	return Base64.getEncoder().encodeToString(bytes);
		}catch(Exception e){
			Log4j2Tool.error(EncryptTool.class, "base64Encode:", e);
			return "";
		}
    }  
    
    /**
     * base64编码
     * @param str 编码字符串
     * @param charsetName 字符集
     * @return
     */
    public static String base64Encode(String str, String charsetName){  
		if ((str==null)||(str.equals(""))) return "";

		try{
	    	byte[] bytes = str.getBytes(charsetName);
	    	return Base64.getEncoder().encodeToString(bytes);
		}catch(Exception e){
			Log4j2Tool.error(EncryptTool.class, "base64Encode:", e);
			return "";
		}
    }  

    /**
     * base64解码
     * 
     * @param encodeStr
     * @return decode string
     */
    public static String base64Decode(String encodeStr) {
    	try{
    		byte[] bytes = encodeStr.getBytes();  
    		return new String(Base64.getDecoder().decode(bytes),"UTF-8");
    	}catch(Exception e){
    		Log4j2Tool.error(EncryptTool.class, "base64Decode:", e);
    		return "";
    	}
    }

    /**
     * base64解码
     * @param encodeStr
     * @return
     */
    public static String base64Decode(String encodeStr, String charsetName) {
    	try{
    		byte[] bytes = encodeStr.getBytes();  
    		return new String(Base64.getDecoder().decode(bytes),charsetName);
    	}catch(Exception e){
    		Log4j2Tool.error(EncryptTool.class, "base64Decode:", e);
    		return "";
    	}
    }

    /**
     * base64解码
     * @param encodeStr
     * @return
     */
    public static byte[] base64DecodeToBytes(String encodeStr) {
    	try{
    		byte[] bytes = encodeStr.getBytes();  
    		return Base64.getDecoder().decode(bytes);
    	}catch(Exception e){
    		Log4j2Tool.error(EncryptTool.class, "base64DecodeToBytes:", e);
    		return null;
    	}
    }

    /**
     * byte 数组转16进制字符串
     * 
     * @param b
     * @return
     */
    private static String byteToString(byte[] b){
        String ret = "";
        String tmp = "";
        for (int n = 0; n < b.length; n++) {
          tmp = (java.lang.Integer.toHexString(b[n] & 0XFF));
          if (tmp.length() == 1)
            ret = ret + "0" + tmp;
          else
            ret = ret + tmp;
        }
        return ret.toUpperCase();
      }

    /**
     * des encrypt
     * @param src source string to be encrypted
     * @param pubKey length>=8
     * @return
     * by @author lms 2017.02.27
     */
    private static byte[] desEncode(byte[] src, byte[] pubKey){
    	try{
    		SecureRandom random = new SecureRandom();
    		DESKeySpec desKey = new DESKeySpec(pubKey);
    		SecretKeyFactory factory = SecretKeyFactory.getInstance(DES);  
    		SecretKey securekey = factory.generateSecret(desKey);
    		Cipher cipher = Cipher.getInstance(DES);
    		cipher.init(Cipher.ENCRYPT_MODE, securekey, random);
    		return cipher.doFinal(src);
        }catch(Exception e){
        	Log4j2Tool.error(EncryptTool.class, "desEncode:", e);
        	return null; 
        }
    }

    /**
     * des encode
     * @param src
     * @param pubKey length>=8
     * @return
     */
    public static String desEncode(String src, String pubKey){
    	return byteToString(desEncode(src.getBytes(), pubKey.getBytes()));
    }

    private static byte[] stringToByte(byte[] b){
        if ((b.length % 2) != 0) return null;  //illegal length
        
        byte[] b2 = new byte[b.length / 2];
        for (int n = 0; n < b.length; n += 2){
        	String item = new String(b, n, 2);
        	b2[n / 2] = (byte) Integer.parseInt(item, 16);
        }
        return b2;
      }

    /**
     * des decode
     * @param src source string to be decrypted
     * @param pubKey key seed
     * @return
     * by @author lms 2017.02.27
     */
    private static byte[] desDecode(byte[] src, byte[] pubKey){
        SecureRandom random = new SecureRandom();  
        try{
        	DESKeySpec desKey = new DESKeySpec(pubKey);
        	SecretKeyFactory factory = SecretKeyFactory.getInstance(DES);
        	SecretKey securekey = factory.generateSecret(desKey);
        	Cipher cipher = Cipher.getInstance(DES);
        	cipher.init(Cipher.DECRYPT_MODE, securekey, random);
        	return cipher.doFinal(src);
        }
        catch(Exception e){
        	Log4j2Tool.error(EncryptTool.class, "desDecode:", e);
        	return null;
        }
    }  
    
    public static String desDecode(String src, String pubKey){
    	return new String(desDecode(stringToByte(src.getBytes()), pubKey.getBytes()));
    }

    /**
     * AES 加密
     * @param src 需要加密原始字符串
     * @param key 秘钥，长度为16
     * @param ivParam 增强秘钥，长度为16
     * @return
     */
    public static String AESEncrypt(String src, String key, String ivParam){  
        String result = "";  
        try {  
            Cipher cipher;  
            cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");  
            byte[] raw = key.getBytes();  
            SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");  
            IvParameterSpec iv = new IvParameterSpec(ivParam.getBytes());// 使用CBC模式，需要一个向量iv，可增加加密算法的强度  
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);  
            byte[] encrypted = cipher.doFinal(src.getBytes("utf-8"));  
            result = Base64.getEncoder().encodeToString(encrypted);  
        } catch (Exception e) {  
        	Log4j2Tool.error(EncryptTool.class, "AESEncrypt:", e);
        }
        return result;
    }  

    /**
     * AES 解密
     * @param src 需要解密字符串
     * @param key 解密秘钥，长度16
     * @param ivParam 解密增强秘钥，长度16
     * @return
     */
    public static String AESDecrypt(String src, String key, String ivParam){
        try {  
            byte[] raw = key.getBytes();  
            SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");  
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");  
            IvParameterSpec iv = new IvParameterSpec(ivParam.getBytes());  
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);  
            byte[] encrypted1 = Base64.getDecoder().decode(src);// 先用base64解密  
            byte[] original = cipher.doFinal(encrypted1);  
            String originalString = new String(original, "utf-8");  
            return originalString;  
        } catch (Exception e) {  
        	Log4j2Tool.error(EncryptTool.class, "AESDecrypt:", e);
            return null;  
        }
    }

	/**
	 * http传输字符编码
	 * 
	 * @param str to be encoded string
	 * @param enc encode style,such as "UTF-8"/"UTF8","GBK"
	 * @return encoded string
	 */
	public static String httpEncode(String str, String enc){
		String ret="";
		if (StrTool.isBlank(str)) return ret;

		if (StrTool.isBlank(enc)) enc= "UTF-8";
		try {
			ret = URLEncoder.encode(str, enc);
		} catch (Exception e) {
			Log4j2Tool.error(EncryptTool.class, "httpEncode:", e);
		}
		return ret;
	}
	
	/**
	 * http传输字符解码
	 * 
	 * @param str to be encoded string
	 * @param enc encode style,such as "UTF-8"/"UTF8","GBK"
	 * @return
	 */
	public static String httpDecode(String str, String enc){
		String ret = "";
		if (StrTool.isBlank(str)) return ret;

		if (StrTool.isBlank(str)) enc = "UTF-8";
		try {
			ret = URLDecoder.decode(str, enc);
		} catch (Exception e) {
			Log4j2Tool.error(EncryptTool.class, "httpDecode:", e);
		}
		return ret;
	}

	/**
	 * to escape string
	 * @param src
	 * @return
	 * by @author lms 2017.02.24
	 */
	public static String escape(String src){
		char ch;
		StringBuffer tmp = new StringBuffer();
		tmp.ensureCapacity(src.length()*6);

		for (int i=0; i<src.length(); i++){
			ch = src.charAt(i);
			if (Character.isDigit(ch) || Character.isLowerCase(ch) || Character.isUpperCase(ch))
				tmp.append(ch);
			else if (ch<256){
				tmp.append("%");
				if (ch<16) tmp.append("0");
				tmp.append(Integer.toString(ch,16));
			}
			else{
				tmp.append("%u");
				tmp.append(Integer.toString(ch,16));
			}
		}
		return tmp.toString();
	}
	
	/**
	 * to unescape string
	 * @param src
	 * @return
	 * by @author lms 2017.02.24
	 */
	public static String unescape(String src){
		StringBuffer tmp = new StringBuffer();
		tmp.ensureCapacity(src.length());
		int lastPos=0,pos=0;
		char ch;
		while (lastPos<src.length()){
			pos = src.indexOf("%",lastPos);
			if (pos == lastPos){
				if (src.charAt(pos+1)=='u'){
					ch = (char)Integer.parseInt(src.substring(pos+2,pos+6),16);
					tmp.append(ch);
					lastPos = pos+6;
				}
				else{
					ch = (char)Integer.parseInt(src.substring(pos+1,pos+3),16);
					tmp.append(ch);
					lastPos = pos+3;
				}
			}
			else{
				if (pos == -1){
					tmp.append(src.substring(lastPos));
					lastPos=src.length();
				}
				else{
					tmp.append(src.substring(lastPos,pos));
					lastPos=pos;
				}
			}
		}
		return tmp.toString();
	}


	/**
	 * 字符串压缩，压缩字符串长度越大，压缩效果越好
	 * @param src 待压缩的字符串
	 * @return
	 */
    public static byte[] gzipString(String src){
    	if (StrTool.isBlank(src)) return null;

    	ByteArrayOutputStream out = new ByteArrayOutputStream();
    	GZIPOutputStream gzip = null;
    	byte[] rets = null;
    	try {
    		gzip = new GZIPOutputStream(out);
    		gzip.write(src.getBytes());
    	} catch (IOException e) {
    		Log4j2Tool.error(EncryptTool.class, "gzipString:", e);
    	}
    	
    	//!!!attention!!! zip必须先关闭,再关闭输出流
    	StreamTool.closeOutputStream(gzip);
    	rets = out.toByteArray();
    	StreamTool.closeOutputStream(out);

    	return rets;
    }

    /**
     * 字节数组解压
     * @param src 待解压的 byte 数组
     * @return
     */
    public static String ungzipBytes(byte[] src) {
        if (src==null || src.length==0)
            return null;

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        GZIPInputStream gunzip = null;
        String ret = null;
        try {
            ByteArrayInputStream in = new ByteArrayInputStream(src);
            gunzip = new GZIPInputStream(in);
            byte[] buffer = new byte[1024];
            int n;
            while ((n = gunzip.read(buffer))!=-1) {
                out.write(buffer, 0, n);
            }
        } catch (IOException e) {
        	Log4j2Tool.error(EncryptTool.class, "ungzipBytes:", e);
        }
        
        StreamTool.closeInputStream(gunzip);
        ret = out.toString();
        StreamTool.closeOutputStream(out);
        return ret;
    }

	/**
	 * gzip压缩的流解压成字符串
	 * @param is
	 * @param charset
	 * @return
	 */
    public static String ungzipStream(InputStream is, String charset) {
    	String ret = null;
    	GZIPInputStream zipis = null;
    	ByteArrayOutputStream baos = new ByteArrayOutputStream();

    	//解压
    	try {
    		zipis = new GZIPInputStream(is);
            int len;
            byte[] buf = new byte[1024];
    		while ((len = zipis.read(buf)) != -1) {
    			baos.write(buf, 0, len);
    		}
        	ret = baos.toString(charset);
    	}catch(Exception e) {
    		Log4j2Tool.error(EncryptTool.class, "ungzipStream:", e);
    	}
    	
    	//关闭流
    	StreamTool.closeInputStream(zipis);
    	StreamTool.closeOutputStream(baos);
    	return ret;
    }
    

    /**
     * Checks if a file is gzipped.
     * 
     * @param f
     * @return
     */
    public static boolean isGZipped(File f) {
        int magic = 0;
        try {
            RandomAccessFile raf = new RandomAccessFile(f, "r");
            magic = raf.read() & 0xff | ((raf.read() << 8) & 0xff00);
            raf.close();
        } catch (Throwable e) {
            e.printStackTrace(System.err);
        }

        return magic == GZIPInputStream.GZIP_MAGIC;
    }

    /**
     * 字符串，随机转大小写
     * 
     * @param str
     * @return
     */
    public static String randomUpperOrLowerCaseString(String str) {
    	if (StrTool.isBlank(str)) 
    		return str;
    	
    	StringBuilder sb = new StringBuilder();
        Random random = new Random();

        char[] array = str.toCharArray();
    	for(char c: array) {
    		if (random.nextBoolean())
    			sb.append(Character.toUpperCase(c));
    		else
    			sb.append(Character.toLowerCase(c));
    	}
    	
    	return sb.toString();
    }
}
